#include "objloader.h"
#include <sstream>
#include <algorithm>
#include <cmath>

Point MultMV(const float* mat, const Point& vec)
{
	Point result;
	for(int i = 0; i != 3; ++i)
	{
		result.coords[i] = 0;
		for(int j = 0; j != 3; ++j)
			result.coords[i] += mat[3*i + j] * vec.coords[j];
	}
	return result;
}

ObjLoader::ObjLoader(void)
{
}

ObjLoader::~ObjLoader(void)
{
}

bool ObjLoader::LoadObjFile(const std::string& fileName)
{
	Clear();

	std::ifstream file(fileName.c_str());

	if( !file.is_open() )
        return false;

	while( !file.eof() )
    {
		std::string string;
		std::getline(file, string);

		std::string data;
		std::stringstream ss(string);
		std::getline(ss, data, ' ');

		if( "mtllib" == data )
        {
			std::string mtlFileName;
			std::copy(fileName.begin(), std::find(fileName.rbegin(), fileName.rend(), '/').base(), std::back_inserter<std::string>(mtlFileName));
			std::getline(ss, data, ' ');
			LoadMTLFile(mtlFileName + data);

        } else if( "v" == data )
		{
			Point vertex;
			for(int i = 0; i != 3; ++i)
			{
				std::getline(ss, data, ' ');
				vertex.coords[i] = atof(data.c_str());
			}
			vertices.push_back( vertex );

		} else if( "vt" == data )
		{
			Point texCoord;
			for(int i = 0; i != 2; ++i)
			{
				std::getline(ss, data, ' ');
				texCoord.coords[i] = atof(data.c_str());
			}
			texCoords.push_back( texCoord );

		} else if( "vn" == data )
		{
			Point normal;
			for(int i = 0; i != 3; ++i)
			{
				std::getline(ss, data, ' ');
				normal.coords[i] = atof(data.c_str());
			}
			normals.push_back( normal );
		}
	}

	Mesh currentMesh;

	file.clear();
	file.seekg(0);

	while( !file.eof() )
    {
		std::string string;
		std::getline(file, string);

		std::string data;
		std::stringstream ss(string);
		std::getline(ss, data, ' ');

		if( "usemtl" == data )
		{
			if( currentMesh.triangles.size() != 0 )
				meshes.push_back(currentMesh);

			currentMesh = Mesh();
			std::getline(ss, data, ' ');
			short matIndex = findMatIndex(data);

			if( matIndex >= 0 )
				currentMesh.matIndex = (unsigned short)matIndex;
			else
				return false;

		} else if( "f" == data )
		{
			Triangle triangle;

			for(int i = 0; i != 3; ++i)
			{
				std::getline(ss, data, ' ');
				std::stringstream datas(data);
				std::string vertexInfo;

				std::getline(datas, vertexInfo, '/');
				triangle.vertexIndices[i] = atoi(vertexInfo.c_str()) - 1;
				std::getline(datas, vertexInfo, '/');
				triangle.texCoord[i] = texCoords[ atoi(vertexInfo.c_str()) - 1];
				std::getline(datas, vertexInfo, '/');
				triangle.normals[i] = normals[ atoi(vertexInfo.c_str()) - 1];
			}

			currentMesh.triangles.push_back(triangle);
		}
	}

	if( currentMesh.triangles.size() != 0 )
				meshes.push_back(currentMesh);

	file.close();

	RotateXAxis(-1.5707963267948966192313216916398);
	Unitize();

	for(int i = 0; i != meshes.size(); ++i)
	{
		for(int j = 0; j != meshes[i].triangles.size(); ++j)
			calcTangent(meshes[i].triangles[j]);
	}

	return true;
}

void ObjLoader::Store(const std::string& fileName)
{
	std::ofstream file(fileName.c_str(), std::ios_base::out | std::ios_base::binary);

	file.write("Visu",5);

	unsigned int vertNum = vertices.size();
	file.write((char*)&vertNum, sizeof(int));
	for(int i = 0; i != vertNum; ++i)
		file.write((char*)vertices[i].coords, sizeof(float)*3);

	unsigned short matNum = materials.size();
	file.write((char*)&matNum, sizeof(short));
	for(int i = 0; i != materials.size(); ++i)
	{
		for(int k = 0; k != 3; ++k)
		{
			float buf = materials[i].diffuse[k];
			file.write((char*)&buf, sizeof(float));
		}

		for(int k = 0; k != 3; ++k)
		{
			float buf = materials[i].diffuse[k];
			file.write((char*)&buf, sizeof(float));
		}

		//file.write((char*)materials[i].diffuse, sizeof(float)*3);
		file.write((char*)materials[i].specular, sizeof(float)*3);
		file.write((char*)&materials[i].shiness, sizeof(float));
		file.write((char*)&materials[i].transparency, sizeof(float));
		file.write(materials[i].texFileName.c_str(), materials[i].texFileName.size() + 1);
		file.write(materials[i].bumpFileName.c_str(), materials[i].bumpFileName.size() + 1);
	}

	unsigned int meshNum = meshes.size();
	file.write((char*)&meshNum, sizeof(int));
	for(int i = 0; i != meshes.size(); ++i)
	{
		file.write((char*)&meshes[i].matIndex, sizeof(short));
		unsigned int triNum = meshes[i].triangles.size();
		file.write((char*)&triNum, sizeof(int));
		for(int j = 0; j != triNum; ++j)
		{
			file.write((char*)meshes[i].triangles[j].vertexIndices, sizeof(int)*3);
			file.write((char*)meshes[i].triangles[j].normals, sizeof(float)*9);
			file.write((char*)meshes[i].triangles[j].texCoord, sizeof(float)*9);
			file.write((char*)&meshes[i].triangles[j].tangent, sizeof(float)*3);
		}
	}

	file.close();
}

bool ObjLoader::LoadMTLFile(const std::string& fileName)
{
	std::ifstream file(fileName.c_str());

	if( !file.is_open() )
        return false;

	std::string currentMaterialName;
	Material currentMaterial;

	while( !file.eof() )
    {
		std::string string;
		std::getline(file, string);

		std::string data;
		std::stringstream ss(string);
		std::getline(ss, data, ' ');

		if( "newmtl" == data )
		{
			if(currentMaterialName.size() != 0)
			{
				materialNames.push_back(currentMaterialName);
				materials.push_back(currentMaterial);
                currentMaterial = Material();
            }

			std::getline(ss, data, ' ');
			currentMaterialName = data;

        } else if( "Ka" == data )
		{
			for(int i = 0; i != 3; ++i)
			{
				std::getline(ss, data, ' ');
				currentMaterial.ambient[i] = atof(data.c_str());
			}

		} else if( "Kd" == data )
		{
			for(int i = 0; i != 3; ++i)
			{
				std::getline(ss, data, ' ');
				currentMaterial.diffuse[i] = atof(data.c_str());
			}

        } else if( "Ks" == data )
		{
			for(int i = 0; i != 3; ++i)
			{
				std::getline(ss, data, ' ');
				currentMaterial.specular[i] = atof(data.c_str());
			}

		} else if( "Ns" == data )
		{
			std::getline(ss, data, ' ');			
			currentMaterial.shiness = atof(data.c_str());

		} else if( "map_Kd" == data )
		{
			std::getline(ss, data, ' ');
			currentMaterial.texFileName = data;

        } else if( "d" == data )
		{
			std::getline(ss, data, ' ');
			currentMaterial.transparency = atof(data.c_str());

		} else if( "map_bump" == data )
		{
			std::getline(ss, data, ' ');
			currentMaterial.bumpFileName = data;
		}
	}

	if(currentMaterialName.size() != 0)
	{
		materialNames.push_back(currentMaterialName);
		materials.push_back(currentMaterial);
	}

	return true;
}

void ObjLoader::Unitize()
{
	float maxx = vertices[0].coords[0];
	float minx = maxx;
	float maxy = vertices[0].coords[1];
	float miny = maxy;
	float maxz = vertices[0].coords[2];
	float minz = maxz;

	for(int i = 0; i != vertices.size(); ++i)
	{
		if(maxx < vertices[i].coords[0])
			maxx = vertices[i].coords[0];
		if(minx > vertices[i].coords[0])
			minx = vertices[i].coords[0];

		if(maxy < vertices[i].coords[1])
			maxy = vertices[i].coords[1];
		if(miny > vertices[i].coords[1])
			miny = vertices[i].coords[1];

		if(maxz < vertices[i].coords[2])
			maxz = vertices[i].coords[2];
		if(minz > vertices[i].coords[2])
			minz = vertices[i].coords[2];
	}

	float w = (maxx > 0 ? maxx : -maxx) + (minx > 0 ? minx : -minx);
	float h = (maxy > 0 ? maxy : -maxy) + (miny > 0 ? miny : -miny);
	float d = (maxz > 0 ? maxz : -maxz) + (minz > 0 ? minz : -minz);

	float cx = (maxx + minx) / 2.0;
	float cy = (maxy + miny) / 2.0;
	float cz = (maxz + minz) / 2.0;

	float scale = 26.0 / d;

	for(int i = 0; i != vertices.size(); ++i)
	{
		vertices[i].coords[0] -= cx;
		vertices[i].coords[1] -= cy;
		vertices[i].coords[2] -= cz;
		vertices[i].coords[0] *= scale;
		vertices[i].coords[1] *= scale;
		vertices[i].coords[2] *= scale;

		vertices[i].coords[1] += 2.5;


	}
}

void ObjLoader::RotateXAxis(const double angle)
{
	float rotMat[3][3] = {1, 0, 0, 0, cos(angle), -sin(angle), 0, sin(angle), cos(angle)};

	for(int i = 0; i != vertices.size(); ++i)
		vertices[i] = MultMV((float*)rotMat, vertices[i]);

	for(int i = 0; i != meshes.size(); ++i)
	{
		for( int j = 0; j != meshes[i].triangles.size(); ++j)
		{
			for(int k = 0; k != 3; ++k)
			{
				meshes[i].triangles[j].normals[k] = MultMV((float*)rotMat, meshes[i].triangles[j].normals[k]);
			}
		}
	}

}

void ObjLoader::RotateYAxis(const double angle)
{
	float rotMat[3][3] = {cos(angle), 0, sin(angle), 0, 1, 0, -sin(angle), 0, cos(angle)};

	for(int i = 0; i != vertices.size(); ++i)
		vertices[i] = MultMV((float*)rotMat, vertices[i]);

	for(int i = 0; i != meshes.size(); ++i)
	{
		for( int j = 0; j != meshes[i].triangles.size(); ++j)
		{
			for(int k = 0; k != 3; ++k)
			{
				meshes[i].triangles[j].normals[k] = MultMV((float*)rotMat, meshes[i].triangles[j].normals[k]);
			}
		}
	}

}

void ObjLoader::RotateZAxis(const double angle)
{
	float rotMat[3][3] = {cos(angle), -sin(angle), sin(angle), cos(angle), 0, 0, 0, 1};

	for(int i = 0; i != vertices.size(); ++i)
		vertices[i] = MultMV((float*)rotMat, vertices[i]);

	for(int i = 0; i != meshes.size(); ++i)
	{
		for( int j = 0; j != meshes[i].triangles.size(); ++j)
		{
			for(int k = 0; k != 3; ++k)
			{
				meshes[i].triangles[j].normals[k] = MultMV((float*)rotMat, meshes[i].triangles[j].normals[k]);
			}
		}
	}

}

void ObjLoader::Clear()
{
	vertices.clear();
    normals.clear();
    texCoords.clear();
    materials.clear();
	materialNames.clear();
    meshes.clear();
}

short ObjLoader::findMatIndex(const std::string& name)
{
	for(short i = 0; i != materialNames.size(); ++i)
	{
		if(materialNames[i] == name)
			return i;
	}
	return -1;
}

void ObjLoader::calcTangent(Triangle& triangle)
{
	Point v1(vertices[triangle.vertexIndices[2]].coords[0] - vertices[triangle.vertexIndices[0]].coords[0],
				vertices[triangle.vertexIndices[2]].coords[1] - vertices[triangle.vertexIndices[0]].coords[1],
				vertices[triangle.vertexIndices[2]].coords[2] - vertices[triangle.vertexIndices[0]].coords[2]);

	Point v2(vertices[triangle.vertexIndices[1]].coords[0] - vertices[triangle.vertexIndices[0]].coords[0],
				vertices[triangle.vertexIndices[1]].coords[1] - vertices[triangle.vertexIndices[0]].coords[1],
				vertices[triangle.vertexIndices[1]].coords[2] - vertices[triangle.vertexIndices[0]].coords[2]);

	Point st1(triangle.texCoord[2].coords[0] - triangle.texCoord[0].coords[0], triangle.texCoord[2].coords[1] - triangle.texCoord[0].coords[1], 0.0);
	Point st2(triangle.texCoord[1].coords[0] - triangle.texCoord[0].coords[0], triangle.texCoord[1].coords[1] - triangle.texCoord[0].coords[1], 0.0);

	float coef = 1 / (st1.coords[0] * st2.coords[1] - st2.coords[0] * st1.coords[1]);

	triangle.tangent.coords[0] = coef * ((v1.coords[0] * st2.coords[1]) + (v2.coords[0] * -st1.coords[1]));
	triangle.tangent.coords[1] = coef * ((v1.coords[1] * st2.coords[1]) + (v2.coords[1] * -st1.coords[1]));
	triangle.tangent.coords[2] = coef * ((v1.coords[2] * st2.coords[1]) + (v2.coords[2] * -st1.coords[1]));
}